package com.hhd.flow.engine.vo;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.hhd.flow.engine.utils.CommonUtils;
import com.hhd.flow.exception.FlowException;
import lombok.Getter;
import lombok.Setter;

import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Getter
@Setter
public class FlowModel {
    private String id;
    private List<FlowNode> nodes;
    private List<FlowEdge> edges;
    private List<GatewayPair> gatewayPairs;
    private String formType;
    private String titleFormula;
    private String titleFormulaText;
    private String beforeListeners;
    private String afterListeners;

    JSONObject jsonModel;

    public static FlowModel forString(String model) {
        JSONObject jsonObject = JSONObject.parseObject(model);
        FlowModel flowModel = jsonObject.toJavaObject(FlowModel.class);
        flowModel.setJsonModel(jsonObject);
        return flowModel;
    }

    public FlowNode getNodeById(String taskDefinitionId) {
        for (FlowNode node : nodes) {
            if (node.getId().equals(taskDefinitionId)) {
                return node;
            }
        }
        return null;
    }

    public List<FlowNodeAndEdge> getNextNodeAndEdgesById(String flowNodeId) {
        return edges.stream()
                .filter(e -> e.getSource().equals(flowNodeId))
                .map(e -> {
                    FlowNodeAndEdge nodeAndEdge = new FlowNodeAndEdge();
                    nodeAndEdge.setEdge(e);
                    nodeAndEdge.setNode(this.getNodeById(e.getTarget()));
                    return nodeAndEdge;
                }).sorted((Comparator.comparingInt(o -> ((FlowNodeAndEdge) o).getEdge().getPriority()).reversed()))
                .collect(Collectors.toList());
    }

    public UserTask getUserTaskById(String id) {
        JSONArray array = jsonModel.getJSONArray("nodes");
        for (int i = 0; i < array.size(); i++) {
            JSONObject jsonObject = array.getJSONObject(i);
            if (id.equals(jsonObject.getString("id"))) {
                return jsonObject.toJavaObject(UserTask.class);
            }
        }
        return null;
    }

    public Gateway getGatewaById(String id) {
        JSONArray array = jsonModel.getJSONArray("nodes");
        Gateway gateway = null;
        for (int i = 0; i < array.size(); i++) {
            JSONObject node = array.getJSONObject(i);
            if (node.getString("id").equals(id) && node.getString("type").equals(FlowNode.FlowNodeType.gateway.name())) {
                gateway = node.toJavaObject(Gateway.class);
                Gateway finalGateway = gateway;
                gatewayPairs.forEach(gp -> {
                    if (gp.getSource().equals(finalGateway.getId())) {
                        finalGateway.setStart(true);
                        finalGateway.setEnd(false);
                    }
                    if (gp.getTarget().equals(finalGateway.getId())) {
                        finalGateway.setStart(false);
                        finalGateway.setEnd(true);
                    }
                });
            }
        }
        return gateway;
    }

    public FlowModel addUserTaskAfter(UserTask userTask, String nodeId) {
        JSONObject newModel = JSONObject.parseObject(jsonModel.toJSONString());
        JSONArray edges = newModel.getJSONArray("edges");
        JSONArray nodes = newModel.getJSONArray("nodes");
        for (int i = 0; i < edges.size(); i++) {
            JSONObject edge = edges.getJSONObject(i);
            if (nodeId.equals(edge.getString("source"))) {
                edge.put("source", userTask.getId());
            }
        }

        nodes.add(userTaskToJSONObject(userTask));
        edges.add(createEdge(nodeId, userTask.getId()));
        return FlowModel.forString(newModel.toJSONString());
    }

    private JSONObject userTaskToJSONObject(UserTask userTask) {
        String jsonString = JSONObject.toJSONString(userTask);
        JSONObject jsonObject = JSONObject.parseObject(jsonString);
        jsonObject.put("notify", 0);
        jsonObject.put("manualSelectUser", 0);
        jsonObject.put("autoComplete", 0);
        jsonObject.put("mustSignature", 0);
        if (userTask.isNotify()) jsonObject.put("notify", 1);
        if (userTask.isManualSelectUser()) jsonObject.put("manualSelectUser", 1);
        if (userTask.isAutoComplete()) jsonObject.put("autoComplete", 1);
        if (userTask.isMustSignature()) jsonObject.put("mustSignature", 1);
        return jsonObject;
    }

    private FlowEdge createEdge(String source, String target) {
        FlowEdge flowEdge = new FlowEdge();
        flowEdge.setId(IdWorker.getIdStr());
        flowEdge.setSource(source);
        flowEdge.setTarget(target);
        flowEdge.setPriority(0);
        flowEdge.setFormula(null);
        flowEdge.setFormula(null);
        flowEdge.setRemark(null);
        return flowEdge;
    }

    public String getModelString() {
        return jsonModel.toJSONString();
    }

    public List<UserTask> getNextCountUserSignsById(UserTask currentUserTaskNode) {
        List<UserTask> res = CommonUtils.newList();
        while (true) {
            List<FlowNodeAndEdge> nodeAndEdges = getNextNodeAndEdgesById(currentUserTaskNode.getId());
            if (CommonUtils.isEmpty(nodeAndEdges)
                    || nodeAndEdges.get(0).getNode().getType() != FlowNode.FlowNodeType.userTask)
                break;
            res.add(currentUserTaskNode = this.getUserTaskById(nodeAndEdges.get(0).getNode().getId()));
        }
        return res;
    }

    public FlowModel removeNodeById(String id) {
        JSONObject newModel = JSONObject.parseObject(jsonModel.toJSONString());
        JSONArray edges = newModel.getJSONArray("edges");
        JSONArray nodes = newModel.getJSONArray("nodes");

        JSONArray newEdges = new JSONArray();
        JSONArray newNodes = new JSONArray();
        for (int i = 0; i < nodes.size(); i++) {
            JSONObject jsonObject = nodes.getJSONObject(i);
            if (id.equals(jsonObject.getString("id"))) continue;
            newNodes.add(jsonObject);
        }

        JSONObject endEdge = null, startEdge = null;
        for (int i = 0; i < edges.size(); i++) {
            JSONObject jsonObject = edges.getJSONObject(i);
            if (id.equals(jsonObject.getString("target"))) {
                startEdge = jsonObject;
            }
            if (id.equals(jsonObject.getString("source"))) {
                endEdge = jsonObject;
                continue;
            } else {
                newEdges.add(jsonObject);
            }
        }
        if (Objects.nonNull(startEdge) && Objects.nonNull(endEdge)) {
            startEdge.put("target", endEdge.getString("target"));
        }
        newModel.put("nodes", newNodes);
        newModel.put("edges", newEdges);
        return FlowModel.forString(newModel.toJSONString());
    }

    public StartNode getStart() {
        JSONArray nodes = jsonModel.getJSONArray("nodes");
        for (int i = 0; i < nodes.size(); i++) {
            JSONObject jsonObject = nodes.getJSONObject(i);
            if ("start".equals(jsonObject.getString("id"))) {
                return jsonObject.toJavaObject(StartNode.class);
            }
        }
        throw new FlowException("没有找到流程开始节点");
    }
}
